Skip to content

fix(claude): stop reformatting all of ~/.claude.json on every apply#718

Closed
mkobit wants to merge 2 commits into
mainfrom
fix/dot-claude-json-diff-noise
Closed

fix(claude): stop reformatting all of ~/.claude.json on every apply#718
mkobit wants to merge 2 commits into
mainfrom
fix/dot-claude-json-diff-noise

Conversation

@mkobit

@mkobit mkobit commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • modify_dot_claude.json.tmpl only ever manages one key (editorMode), but json.dumps(merged, indent=2, sort_keys=True) reformatted the entire ~2000-line file on every apply: sort_keys=True reordered every key, and the default ensure_ascii=True escaped all non-ASCII (··). Claude Code itself writes the file with its own key order and literal UTF-8, so the two formats fought every apply, burying any real change in a huge diff.
  • Drops sort_keys, adds ensure_ascii=False — matches Claude Code's own formatting (already verified: 2-space indent, no trailing newline, both already correct).

Test plan

  • Rendered the script and ran it against the live ~/.claude.json — output is byte-identical (zero diff) since editorMode is already correct.
  • Simulated a real editorMode change in a copy of the live file and re-ran — exactly one line differs, no reordering/escaping noise.

sort_keys=True and default ensure_ascii=True made every apply rewrite
the whole ~2000-line file (key reorder + unicode escaping) even
though only editorMode is ever actually managed -- Claude Code itself
writes unsorted keys and literal UTF-8, so the two formats fought on
every apply, producing huge diffs that made real changes invisible in
the noise.

Verified: with editorMode already correct, output is now byte-
identical to the live file (zero diff). With a simulated real change,
exactly one line differs.
mkobit added a commit that referenced this pull request Jul 15, 2026
editorMode is a documented top-level settings.json key
(code.claude.com/docs/en/settings); ~/.claude.json is documented as
OAuth session / MCP config / per-project trust state / caches, not a
settings store. The current claude_code.preferences ->
modify_dot_claude.json.tmpl injection (PR #718) targets the wrong
file, same category of bug as the tools/files no-op keys already
removed from this same settings block.

Confirmed settings.json is the real, effective location before
adding this: the live ~/.claude/settings.json already had
editorMode = "vim" (from toggling /config previously, not from any
chezmoi-managed source) -- Claude Code itself reads/writes it there.

Also fixed a stale comment referencing the old
ai.command_policy.families / single-file .chezmoidata/ai/command_policy.toml
shape, superseded by this branch's flat commands dict under
.chezmoidata/ai/command_policy/*.toml.

Not removing the ~/.claude.json injection yet -- that file is
modified on the separate open PR #718 branch; removing
claude_code.preferences here risks a merge-order-dependent break of
that script. Left as an explicit follow-up for after both PRs merge.
New convention in root AGENTS.md: pure chezmoi:modify-template
(fromJson/fromToml -> template logic -> toPrettyJson/toToml) is now
the default shape for new modify_ scripts, established by
dot_codex/modify_config.toml and the two settings.json scripts
converted alongside this branch's work. Documents the two sharp edges
found empirically (no .tmpl suffix, no executable bit -- both break
chezmoi's dispatch silently) and the trimSuffix "\n" fix for
toPrettyJson's baked-in trailing newline.

Also documents modify_dot_claude.json.tmpl as the deliberate
exception, with the comment already added directly in that file:
Go's fromJson/toPrettyJson always alphabetize keys, which would
reintroduce the exact whole-file reformatting diff noise this
branch's fix removes.
mkobit added a commit that referenced this pull request Jul 15, 2026
…njection

editorMode was the only key claude_code.preferences ever injected into
~/.claude.json via modify_dot_claude.json.tmpl. Now that it lives in
claude_code.settings (-> ~/.claude/settings.json, the documented
location, previous commit), that injection is a pure no-op -- and
~/.claude.json itself is documented as OAuth session / MCP config /
trust state / caches, not a place a dotfiles repo should be
templating content into at all.

Deletes modify_dot_claude.json.tmpl and claude_code.preferences
outright rather than leaving inert infrastructure around. This also
makes moot a separate bug found while investigating: the file had
lost its executable bit (required for chezmoi's legacy external-
script modify_ mechanism) and chezmoi diff was rendering its raw
unexecuted Python source as the literal target content -- would have
replaced the live ~2000-line ~/.claude.json with ~46 lines of garbage
had it ever been applied in that state. Deleting the file removes the
bug's entire surface rather than patching a mechanism with no reason
to exist anymore.

Updates dot_claude/AGENTS.md (single managed file now, not two) and
the stale modify_dot_claude.json.tmpl cross-reference in
dot_claude/modify_settings.json.

Supersedes PR #718, which was fixing formatting on the now-deleted
file -- closing that PR instead of merging it.
mkobit added a commit that referenced this pull request Jul 15, 2026
Ported from PR #718 (being closed, superseded -- the file it fixed,
modify_dot_claude.json.tmpl, was deleted outright in the previous
commit rather than patched). Documents the two sharp edges found
empirically (no .tmpl suffix, no executable bit -- both break
chezmoi's dispatch silently) and the trimSuffix "\n" fix for
toPrettyJson's baked-in trailing newline, plus the general key-
ordering caveat for files another live process also writes to.
@mkobit mkobit closed this Jul 15, 2026
@mkobit mkobit deleted the fix/dot-claude-json-diff-noise branch July 15, 2026 06:12
mkobit added a commit that referenced this pull request Jul 15, 2026
…717)

* refactor(ai): flatten command_policy to a command->bool dict

Families + prefixes + add/remove/enabled all existed to work around
chezmoi replacing list values wholesale on collision. Storing entries
as dict keys instead removes the problem at the root: an overlay adds
a command by adding a key, or removes one by setting it false — plain
dict merge, same pattern .chezmoidata/ai/sandbox.toml already uses.

Renders byte-identical output to the schema this replaces (verified
against the live ~/.claude/settings.json and
~/.gemini/antigravity-cli/settings.json applied in PR #716) — pure
schema simplification, no behavior change.

* feat(ai): allow/ask/deny tri-state for command_policy commands

Replaces the true/false value with a string mode matching both tools'
real permission tiers (Claude Code has permissions.allow/.deny/.ask;
Antigravity's docs state "Deny > Ask > Allow" precedence). "ask" (or
an unset command) renders nowhere in either tool -- absence from
allow/deny is what falls back to a normal per-use prompt.

All current commands stay "allow" -- no behavior change, verified by
re-rendering against the live settings files with zero diff. deny
rendering is wired but unexercised (no command uses it yet); the
Antigravity deny(...) key name is inferred from documented precedence
language, not yet live-tested, flagged as a comment in the template.

* docs(ai): move command_policy detail to src/chezmoi/AGENTS.md

Root AGENTS.md keeps a 1-2 sentence summary + link; full schema/
rendering/overlay detail moves to src/chezmoi/AGENTS.md, next to the
Notable cross-cutting features bullet (matches the existing sandboxr
pattern). Not placed inside .chezmoidata/ai/command_policy/ itself --
tried that first and it breaks every template reading chezmoi data
(chezmoi parses every file under .chezmoidata/ as a data source; a
.md file there errors with "unknown format").

* docs(ai): use markdown links instead of backtick paths for cross-references

* fix(ai): antigravity permissions fully replaced, not union-merged

User preference: chezmoi should own permissions.allow/.deny outright
rather than preserving whatever agy's own "always allow" flow wrote
into the live file -- matches Claude's existing full-replace behavior.

Also fixes a real bug this surfaced: $agPermissions (built to carry
an optional "deny" key) was computed but never wired into the actual
$settings dict, which still built permissions inline with only
"allow" -- deny was dead code for this tool, unverified until now.
Confirmed working with a throwaway test entry (not committed).

* refactor(ai): convert both settings.json modify scripts to pure chezmoi:modify-template

Drops the embedded-Python round-trip entirely for both
dot_claude/modify_settings.json and
dot_gemini/antigravity-cli/modify_settings.json, matching the
existing dot_codex/modify_config.toml precedent (fromJson .chezmoi.stdin
-> template logic -> toPrettyJson). Requires dropping both the .tmpl
suffix and the executable bit -- chezmoi's chezmoi:modify-template
dispatch doesn't recognize the file otherwise (confirmed empirically;
the executable-script code path silently takes over instead and
.chezmoi.stdin never gets populated).

toPrettyJson's return value always carries a trailing "\n" that trim
markers can't remove (they only affect surrounding template text, not
a pipeline's runtime value) -- piped through trimSuffix "\n" to match
the no-trailing-newline convention both live files already use.

No behavior change: both verified via chezmoi diff against the live
settings files (zero diff), including the deny bucket (tested with a
throwaway, uncommitted data file, both tools).

* fix(claude): move editorMode to the documented settings.json location

editorMode is a documented top-level settings.json key
(code.claude.com/docs/en/settings); ~/.claude.json is documented as
OAuth session / MCP config / per-project trust state / caches, not a
settings store. The current claude_code.preferences ->
modify_dot_claude.json.tmpl injection (PR #718) targets the wrong
file, same category of bug as the tools/files no-op keys already
removed from this same settings block.

Confirmed settings.json is the real, effective location before
adding this: the live ~/.claude/settings.json already had
editorMode = "vim" (from toggling /config previously, not from any
chezmoi-managed source) -- Claude Code itself reads/writes it there.

Also fixed a stale comment referencing the old
ai.command_policy.families / single-file .chezmoidata/ai/command_policy.toml
shape, superseded by this branch's flat commands dict under
.chezmoidata/ai/command_policy/*.toml.

Not removing the ~/.claude.json injection yet -- that file is
modified on the separate open PR #718 branch; removing
claude_code.preferences here risks a merge-order-dependent break of
that script. Left as an explicit follow-up for after both PRs merge.

* fix(claude): stop managing ~/.claude.json entirely, delete the dead injection

editorMode was the only key claude_code.preferences ever injected into
~/.claude.json via modify_dot_claude.json.tmpl. Now that it lives in
claude_code.settings (-> ~/.claude/settings.json, the documented
location, previous commit), that injection is a pure no-op -- and
~/.claude.json itself is documented as OAuth session / MCP config /
trust state / caches, not a place a dotfiles repo should be
templating content into at all.

Deletes modify_dot_claude.json.tmpl and claude_code.preferences
outright rather than leaving inert infrastructure around. This also
makes moot a separate bug found while investigating: the file had
lost its executable bit (required for chezmoi's legacy external-
script modify_ mechanism) and chezmoi diff was rendering its raw
unexecuted Python source as the literal target content -- would have
replaced the live ~2000-line ~/.claude.json with ~46 lines of garbage
had it ever been applied in that state. Deleting the file removes the
bug's entire surface rather than patching a mechanism with no reason
to exist anymore.

Updates dot_claude/AGENTS.md (single managed file now, not two) and
the stale modify_dot_claude.json.tmpl cross-reference in
dot_claude/modify_settings.json.

Supersedes PR #718, which was fixing formatting on the now-deleted
file -- closing that PR instead of merging it.

* docs: default modify_ scripts to chezmoi:modify-template

Ported from PR #718 (being closed, superseded -- the file it fixed,
modify_dot_claude.json.tmpl, was deleted outright in the previous
commit rather than patched). Documents the two sharp edges found
empirically (no .tmpl suffix, no executable bit -- both break
chezmoi's dispatch silently) and the trimSuffix "\n" fix for
toPrettyJson's baked-in trailing newline, plus the general key-
ordering caveat for files another live process also writes to.

* docs: reformat AGENTS.md prose to one sentence per line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant